module ActiveMerchant
  module Billing
    module AuthorizeNetCimAPI
      module Address
        def self.included(base)
          base.scalar_attributes.concat SCALAR_ATTRIBUTES
        end

        def self.default_conditions(max=50)
          [false, /^.{1,#{max}}$/, "must be between 1 and #{max} characters (no symbols)"]
        end

        SCALAR_ATTRIBUTES = [ [ :firstName, *default_conditions],
                              [ :lastName, *default_conditions],
                              [ :company, *default_conditions],
                              [ :address, *default_conditions(60)],
                              [ :city, *default_conditions(40)],
                              [ :state, false, /^\w\w$/, "must be a two character state code"],
                              [ :zip, *default_conditions(20)],
                              [ :country, *default_conditions(60)]
                            ]
      end
      
      class CreditCard < Base
        self.scalar_attributes = [ [ :cardNumber, true, /^\d{13,16}$/, "must be between 13 and 16 digits" ],
                                   [ :expirationDate, true, /^\d\d\d\d-\d\d$/, "must be of the format YYYY-MM" ]
                                 ]
        self.composite_attributes = []
        
        #include ActiveMerchant::Billing::CreditCardMethods
      end

      class BankAccount < Base
        self.scalar_attributes = [ [ :accountType, false, [:checking, :savings, :businessChecking]],
                              [ :nameOnAccount, true, 22],
                              [ :echeckType, false, [:CCD, :PPD, :TEL, :WEB]],
                              [ :bankName, false, 50],
                              [ :routingNumber, true, /^\d{9}$/, "must be 9 digits"],
                              [ :accountNumber, true, /^\d{5,17}$/, "must be between 5 and 17 digits"]
                            ]

        self.composite_attributes = []             
      end

      class ShipToList < Base
        include Address

        self.scalar_attributes.concat [ [ :phoneNumber, false, /^[ ()\-\d]{1,25}$/, "must be between 1 and 25 digits" ],
                                        [ :faxNumber, false, /^[ ()\-\d]{1,25}$/, "must be between 1 and 25 digits"],
                                        [ :customerAddressId, false, /^\d+$/, 'must be a number']
                                      ]
      end

      class BillTo < Base
        include Address

        self.scalar_attributes.concat [ [ :phoneNumber, false, /^[ ()\-\d]{1,25}$/, "must be between 1 and 25 digits" ],
                                        [ :faxNumber, false, /^[ ()\-\d]{1,25}$/, "must be between 1 and 25 digits"]
                                      ]

      end

      class Payment < Base
        self.scalar_attributes = []

        self.composite_attributes = [ [ :creditCard, false, CreditCard],
                                      [ :bankAccount, false, BankAccount]
                                    ]

        def validate_custom
          if (@creditCard.blank? && @bankAccount.blank?) || (@creditCard && @bankAccount)
            errors.add_to_base 'You must supply either a creditCard or a bankAccount (not both)'
          end
        end
      end

      class PaymentProfile < Base
        self.scalar_attributes = [ [ :customerType, false, [:individual, :business] ], 
                                   [ :taxId, false, /^\d{9}$/, 'must be 9 digits'], # only required for Wells Fargo SecureSource eCheck.Net merchants
                                   [ :customerPaymentProfileId, false, /^\d*$/, 'must be all digits']
                                 ]

        self.composite_attributes = [ [ :billTo, false, BillTo],
                                      [ :payment, true, Payment]
                                      #[ :driversLicense, false] # only required for Wells Fargo SecureSource eCheck.Net merchants
                               ]
      end


      class CustomerProfile < Base
        self.scalar_attributes = [ [ :merchantCustomerId, false, 20],
                                   [ :description, false, 255],
                                   [ :email, false, 255],
                                   [ :customerProfileId, false, /^\d*$/, "must be all digits"]
                                 ]

        self.composite_attributes = [ [ :paymentProfiles, false, PaymentProfile],
                                      [ :shipToList, false, ShipToList]
                                    ]

        def validate_custom
          attribs = scalar_attributes.map{|a| a.first}
          if attribs.all? {|attr| send(attr).blank? }
            errors.add_to_base "You must specify one of: #{attribs.join(', ')}"
          end
        end                            
      end

      class PaymentData < Base
        self.scalar_attributes = [ [ :amount, false, 7], # API Says "Up to 4 digits with a decimal?"
                                   [ :name, false, 31],
                                   [ :description, false, 255]
                                 ]
      end
      
      class LineItem < Base
        self.scalar_attributes = [ [ :itemId, false, 31],
                                   [ :name, false, 31],
                                   [ :description, false, 255],
                                   [ :quantity, false, 5],
                                   [ :unitPrice, false, 5],
                                   [ :taxable, false, [:TRUE, :FALSE] ]
                                 ]
                                   
      end

      class Order < Base
        self.scalar_attributes = [ [ :invoiceNumber, false, 20],
                                   [ :description, false, 255],
                                   [ :purchaseOrderNumber, false, 25]                                   
                                 ]        
      end

      class Transaction < Base
        self.scalar_attributes = [ [ :amount, true, 7],  # API Says "Up to 4 digits with a decimal?"
                                   [ :customerProfileId, true, /^\d*$/, "must be all digits"],
                                   [ :customerPaymentProfileId, true, /^\d*$/, "must be all digits"],
                                   [ :customerShippingAddressId, false, /^\d*$/, "must be all digits"],
                                   [ :taxExempt, false, [:TRUE, :FALSE] ],
                                   [ :recurringBilling, false, [:TRUE, :FALSE] ],
                                   [ :cardCode, false, /^\d{3,4}$/, 'must be 3 or 4 digits'], # Only required if using CCV filter
                                   [ :approvalCode, false, 6], # Only required for Capture Only transaction type
                                   [ :type, true, [:CaptureOnly, :AuthCapture, :AuthOnly] ]
                                 ]
                                 
        self.composite_attributes = [ [ :tax, false, PaymentData],
                                     [ :shipping, false, PaymentData],
                                     [ :duty, false, PaymentData],
                                     [ :lineItems, false, LineItem],
                                     [ :order, false, Order]
                                   ]
        
        # type is not actually a valid attribute for the API
        # so we set it to nil and 'replace' it with the valid
        # identifier 
        alias_method :old_to_xml, :to_xml
        def to_xml(xml)
          xml ||= Builder::XmlMarkup.new :indent => 2
          type, @type = @type, nil
          
          xml.tag! "profileTrans#{type}" do
            old_to_xml(xml)
          end
          
          @type = type
          xml
        end
        
      end

      class Authentication < Base
        self.scalar_attributes = [ [:name, true, 25],
                                   [:transactionKey, true, /^.{16}$/, "must be exactly 16 characters"]
                                 ]
        self.composite_attributes = []
      end
    end
  end
end